/*=============================================================================

  Library: HMPBundleFrame

  Copyright (c) German Cancer Research Center,
    Division of Medical and Biological Informatics

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.

=============================================================================*/

#include "BundleFrameworkListeners_p.h"

#include "Exception.h"
#include "BundleFrameworkContext_p.h"
#include "BundleConstants.h"
#include "LDAPExpr_p.h"
#include "ServiceReference_p.h"

#include <QStringListIterator>
#include <QDebug>

const int PluginFrameworkListeners::OBJECTCLASS_IX = 0;
const int PluginFrameworkListeners::SERVICE_ID_IX = 1;
const int PluginFrameworkListeners::SERVICE_PID_IX = 2;

//----------------------------------------------------------------------------
PluginFrameworkListeners::PluginFrameworkListeners(BundleFrameworkContext* pluginFw)
  : pluginFw(pluginFw)
{
  hashedServiceKeys << BundleConstants::OBJECTCLASS.toLower()
      << BundleConstants::SERVICE_ID.toLower()
      << BundleConstants::SERVICE_PID.toLower();

  for (int i = 0; i < hashedServiceKeys.size(); ++i)
  {
    cache.push_back(QHash<QString, QList<ServiceSlotEntry> >());
  }
}

//----------------------------------------------------------------------------
void PluginFrameworkListeners::addServiceSlot(
    QSharedPointer<IBundle> plugin, QObject* receiver,
    const char* slot, const QString& filter)
{
  QMutexLocker lock(&mutex); Q_UNUSED(lock)
  ServiceSlotEntry sse(plugin, receiver, slot, filter);
  if (serviceSet.contains(sse))
  {
    removeServiceSlot_unlocked(plugin, receiver, slot);
  }
  serviceSet.insert(sse);
  checkSimple(sse);

  connect(receiver, SIGNAL(destroyed(QObject*)), this, SLOT(serviceListenerDestroyed(QObject*)), Qt::DirectConnection);
}

//----------------------------------------------------------------------------
void PluginFrameworkListeners::removeServiceSlot(QSharedPointer<IBundle> plugin,
                                                    QObject* receiver,
                                                    const char* slot)
{
  QMutexLocker lock(&mutex);
  removeServiceSlot_unlocked(plugin, receiver, slot);
}

//----------------------------------------------------------------------------
void PluginFrameworkListeners::removeServiceSlot_unlocked(QSharedPointer<IBundle> plugin,
                                                             QObject* receiver,
                                                             const char* slot)
{
  ServiceSlotEntry entryToRemove(plugin, receiver, slot);
  QMutableSetIterator<ServiceSlotEntry> it(serviceSet);
  while (it.hasNext())
  {
    ServiceSlotEntry currentEntry = it.next();
    if (currentEntry == entryToRemove)
    {
      currentEntry.setRemoved(true);
      //listeners.framework.hooks.handleServiceListenerUnreg(sle);
      removeFromCache(currentEntry);
      it.remove();
      if (slot) break;
    }
  }

  if (plugin)
  {
    disconnect(receiver, SIGNAL(destroyed(QObject*)), this, SLOT(serviceListenerDestroyed(QObject*)));
  }
}

//----------------------------------------------------------------------------
void PluginFrameworkListeners::serviceListenerDestroyed(QObject *listener)
{
  this->removeServiceSlot(QSharedPointer<IBundle>(0), listener, 0);
}

//----------------------------------------------------------------------------
QSet<ServiceSlotEntry> PluginFrameworkListeners::getMatchingServiceSlots(
    const IServiceReference& sr, bool lockProps)
{
  QMutexLocker lock(&mutex); Q_UNUSED(lock);

  QSet<ServiceSlotEntry> set;
  set.reserve(serviceSet.size());
  // Check complicated or empty listener filters
  int n = 0;
  LDAPExpr expr;
  foreach (const ServiceSlotEntry& sse, complicatedListeners)
  {
    ++n;
    expr = sse.getLDAPExpr();
    if (expr.isNull() || expr.evaluate(sr.d_func()->getProperties(), false))
    {
      set.insert(sse);
    }
  }

  if (pluginFw->debug.ldap)
  {
    qDebug() << "Added" << set.size() << "out of" << n
      << "listeners with complicated filters";
  }

  // Check the cache
  QStringList c = sr.d_func()->getProperty(BundleConstants::OBJECTCLASS, lockProps).toStringList();
  foreach (QString objClass, c)
  {
    addToSet(set, OBJECTCLASS_IX, objClass);
  }

  bool ok = false;
  qlonglong service_id = sr.d_func()->getProperty(BundleConstants::SERVICE_ID, lockProps).toLongLong(&ok);
  if (ok)
  {
    addToSet(set, SERVICE_ID_IX, QString::number(service_id));
  }

  QStringList service_pids = sr.d_func()->getProperty(BundleConstants::SERVICE_PID, lockProps).toStringList();
  foreach (QString service_pid, service_pids)
  {
    addToSet(set, SERVICE_PID_IX, service_pid);
  }

  return set;
}

//----------------------------------------------------------------------------
void PluginFrameworkListeners::frameworkError(QSharedPointer<IBundle> p, const Exception& e)
{
  emit frameworkEvent(BundleFrameworkEvent(BundleFrameworkEvent::PLUGIN_ERROR, p, e));
}

//----------------------------------------------------------------------------
void PluginFrameworkListeners::emitFrameworkEvent(const BundleFrameworkEvent& event)
{
  emit frameworkEvent(event);
}

//----------------------------------------------------------------------------
void PluginFrameworkListeners::emitPluginChanged(const BundleEvent& event)
{
  emit pluginChangedDirect(event);

  if (!(event.getType() == BundleEvent::STARTING ||
      event.getType() == BundleEvent::STOPPING ||
      event.getType() == BundleEvent::LAZY_ACTIVATION))
  {
    emit pluginChangedQueued(event);
  }
}

//----------------------------------------------------------------------------
void PluginFrameworkListeners::serviceChanged(
    const QSet<ServiceSlotEntry>& receivers,
    const ServiceEvent& evt)
{
  QSet<ServiceSlotEntry> matchBefore;
  serviceChanged(receivers, evt, matchBefore);
}

//----------------------------------------------------------------------------
void PluginFrameworkListeners::serviceChanged(
    const QSet<ServiceSlotEntry>& receivers,
    const ServiceEvent& evt,
    QSet<ServiceSlotEntry>& matchBefore)
{
  IServiceReference sr = evt.getServiceReference();
  //QStringList classes = sr.getProperty(BundleConstants::OBJECTCLASS).toStringList();
  int n = 0;

  //framework.hooks.filterServiceEventReceivers(evt, receivers);

  foreach (ServiceSlotEntry l, receivers)
  {
    if (!matchBefore.isEmpty())
    {
      matchBefore.remove(l);
    }

    // TODO permission checks
    //if (l.IBundle.hasPermission(new ServicePermission(sr, ServicePermission.GET))) {
    //foreach (QString clazz, classes)
    //{
    try
    {
      ++n;
      l.invokeSlot(evt);
    }
    catch (const Exception& pe)
    {
      frameworkError(l.getPlugin(), pe);
    }
    catch (const std::exception& e)
    {
      frameworkError(l.getPlugin(), RuntimeException(e.what()));
    }

    //break;
    //}
    //}
  }

  if (pluginFw->debug.ldap)
  {
    qDebug() << "Notified" << n << " listeners";
  }
}

//----------------------------------------------------------------------------
void PluginFrameworkListeners::removeFromCache(const ServiceSlotEntry& sse)
{
  if (!sse.getLocalCache().isEmpty())
  {
    for (int i = 0; i < hashedServiceKeys.size(); ++i)
    {
      QHash<QString, QList<ServiceSlotEntry> >& keymap = cache[i];
      QStringList& l = sse.getLocalCache()[i];
      QStringListIterator it(l);
      while (it.hasNext())
      {
        QString value = it.next();
        QList<ServiceSlotEntry>& sses = keymap[value];
        sses.removeAll(sse);
        if (sses.isEmpty())
        {
          keymap.remove(value);
        }
      }
    }
  }
  else
  {
    complicatedListeners.removeAll(sse);
  }
}

//----------------------------------------------------------------------------
void PluginFrameworkListeners::checkSimple(const ServiceSlotEntry& sse)
{
  if (sse.getLDAPExpr().isNull()) // || listeners.nocacheldap) {
  {
    complicatedListeners.push_back(sse);
  }
  else
  {
    LDAPExpr::LocalCache local_cache;
    if (sse.getLDAPExpr().isSimple(hashedServiceKeys, local_cache, false))
    {
      sse.getLocalCache() = local_cache;
      for (int i = 0; i < hashedServiceKeys.size(); ++i)
      {
        QStringListIterator it(local_cache[i]);
        while (it.hasNext())
        {
          QString value = it.next();
          QList<ServiceSlotEntry>& sses = cache[i][value];
          sses.push_back(sse);
        }
      }
    }
    else
    {
      if (pluginFw->debug.ldap)
      {
        qDebug() << "## DEBUG: Too complicated filter:" << sse.getFilter();
      }
      complicatedListeners.push_back(sse);
    }
  }
}

//----------------------------------------------------------------------------
void PluginFrameworkListeners::addToSet(QSet<ServiceSlotEntry>& set,
                                           int cache_ix, const QString& val)
{
  QList<ServiceSlotEntry>& l = cache[cache_ix][val];
  if (!l.isEmpty())
  {
    if (pluginFw->debug.ldap)
    {
      qDebug() << hashedServiceKeys[cache_ix] << "matches" << l.size();
    }
    foreach (ServiceSlotEntry entry, l)
    {
      set.insert(entry);
    }
  }
  else
  {
    if (pluginFw->debug.ldap)
    {
      qDebug() << hashedServiceKeys[cache_ix] << "matches none";
    }
  }
}
